package beautiful.butterfly.distributed_service_container.http.mvc.http;

import beautiful.butterfly.distributed_service_container.application.Application;
import beautiful.butterfly.distributed_service_container.http.handlers.HttpConst;
import beautiful.butterfly.distributed_service_container.http.mvc.Constant;
import beautiful.butterfly.distributed_service_container.http.mvc.core.ActionContext;
import beautiful.butterfly.distributed_service_container.http.mvc.render.Render;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;


//
public class HttpResponse implements Response
{
    private static final Logger log = LoggerFactory.getLogger(System.class);

    private HttpHeaders httpHeaders = new DefaultHttpHeaders(false);
    private Set<Cookie> cookieSet = new HashSet<Cookie>(4);


    private ChannelHandlerContext channelHandlerContext = null;
    private CharSequence contentType = null;
    private CharSequence dateString = null;
    //
    private boolean isCommit = false;
    private int statusCode = 200;

    //
    public static HttpResponse build(ChannelHandlerContext channelHandlerContext, CharSequence dateString)
    {
        HttpResponse httpResponse = new HttpResponse();
        httpResponse.channelHandlerContext = channelHandlerContext;
        httpResponse.dateString = dateString;
        return httpResponse;
    }

    @Override
    public boolean isCommit()
    {
        return isCommit;
    }

    @Override
    public Response setStatusCode(int statusCode)
    {
        this.statusCode = statusCode;
        return this;
    }

    @Override
    public int getStatusCode()
    {
        return this.statusCode;
    }

    @Override
    public Response setContentType(@NonNull CharSequence contentType)
    {
        this.contentType = contentType;
        return this;
    }

    /**
     * 核心方法
     */


    CharSequence getContentType(CharSequence contentType)
    {
        if (null == contentType)
        {
            contentType = HttpConst.CONTENT_TYPE_HTML;
        }
        if (HttpConst.contentTypes.containsKey(contentType))
        {
            return HttpConst.contentTypes.get(contentType);
        }
        HttpConst.contentTypes.put(contentType, AsciiString.of(String.valueOf(contentType)));
        return HttpConst.contentTypes.get(contentType);
    }

    @Override
    public String getContentType()
    {
        return null == this.contentType ? null : String.valueOf(this.contentType);
    }

    @Override
    public Response setHeader(CharSequence name, CharSequence value)
    {
        this.httpHeaders.set(name, value);
        return this;
    }

    @Override
    public Map<String, String> getHeaderMap()
    {
        Map<String, String> map = new HashMap<>(this.httpHeaders.size());
        for (Map.Entry<CharSequence, CharSequence> entry : this.httpHeaders.entries())
        {
            map.put(entry.getKey().toString(), entry.getValue().toString());
        }
        return map;
    }


    @Override
    public Response addCookie(@NonNull Cookie cookie)
    {
        this.cookieSet.add(cookie);
        return this;
    }

    @Override
    public Response addCookie(String name, String value)
    {
        Cookie cookie = new DefaultCookie(name, value);
        this.cookieSet.add(cookie);
        return this;
    }

    @Override
    public Response addCookie(@NonNull String name, @NonNull String value, int maxAge)
    {
        Cookie cookie = new DefaultCookie(name, value);
        cookie.setPath("/");
        cookie.setMaxAge(maxAge);
        this.cookieSet.add(cookie);
        return this;
    }

    @Override
    public Response addCookie(@NonNull String name, @NonNull String value, int maxAge, boolean secured)
    {
        Cookie cookie = new DefaultCookie(name, value);
        cookie.setPath("/");
        cookie.setMaxAge(maxAge);
        cookie.setSecure(secured);
        this.cookieSet.add(cookie);
        return this;
    }

    @Override
    public Response addCookie(@NonNull String path, @NonNull String name, @NonNull String value, int maxAge, boolean secured)
    {
        Cookie cookie = new DefaultCookie(name, value);
        cookie.setMaxAge(maxAge);
        cookie.setSecure(secured);
        cookie.setPath(path);
        this.cookieSet.add(cookie);
        return this;
    }

    @Override
    public Response removeCookie(@NonNull String name)
    {

        Iterator<Cookie> iterator = this.cookieSet.iterator();
        while (iterator.hasNext())
        {
            Cookie cookie = iterator.next();
            if (cookie.name().equals(name))
            {
                cookie.setValue("");
                cookie.setMaxAge(-1);
                break;
            }
        }

        Cookie cookie = new DefaultCookie(name, "");
        cookie.setMaxAge(-1);
        this.cookieSet.add((io.netty.handler.codec.http.Cookie) cookie);
        return this;
    }

    @Override
    public Map<String, String> getCookieMap()
    {
        Map<String, String> map = new HashMap<>(8);

        Iterator<Cookie> iterator = this.cookieSet.iterator();
        while (iterator.hasNext())
        {
            Cookie cookie = iterator.next();
            map.put(cookie.name(), cookie.value());
        }
        return map;
    }


    @Override
    public FullHttpResponse send(@NonNull FullHttpResponse fullHttpResponse)
    {
        this.cookieSet.addAll(ActionContext.getRequest().getNameToCookieMap().values());
        fullHttpResponse.headers().set(getDefaultHeader());
        //
        boolean keepAlive = false;

        if (!fullHttpResponse.headers().contains(HttpConst.CONTENT_LENGTH))
        {
            fullHttpResponse.headers().set(HttpConst.CONTENT_LENGTH, String.valueOf(fullHttpResponse.content().readableBytes()));
        }
        if (!keepAlive)
        {
            channelHandlerContext.write(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
        } else
        {
            fullHttpResponse.headers().set(HttpConst.CONNECTION, KEEP_ALIVE);
            channelHandlerContext.write(fullHttpResponse, channelHandlerContext.voidPromise());
        }
        isCommit = true;
        return fullHttpResponse;
    }

    private HttpHeaders getDefaultHeader()
    {
        httpHeaders.set(HttpConst.DATE, HttpConst.DATE);
        httpHeaders.set(HttpConst.CONTENT_TYPE, getContentType(this.contentType));
        httpHeaders.set(HttpConst.X_POWER_BY, HttpConst.VERSION);
        if (!httpHeaders.contains(HttpConst.SERVER))
        {
            httpHeaders.set(HttpConst.SERVER, HttpConst.VERSION);
        }
        if (this.cookieSet.size() > 0)
        {

            Iterator<Cookie> iterator = this.cookieSet.iterator();
            while (iterator.hasNext())
            {
                Cookie cookie = iterator.next();
                if (Application.developMode)
                {
                    log.info("name:" + cookie.name() + "," + cookie.value());
                }
                httpHeaders.add(HttpConst.set_cookie, ServerCookieEncoder.encode(cookie));
            }
        }
        return httpHeaders;
    }

    public void render(@NonNull Render render)
    {
        try
        {
            ByteBuf byteBuf = Unpooled.wrappedBuffer(render.render().toString().getBytes("utf-8"));
            FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(statusCode), byteBuf);
            this.send(fullHttpResponse);
        } catch (Exception e)
        {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }


    @Override
    public void redirect(@NonNull String url)
    {
        httpHeaders.set(HttpConst.LOCATION, url);
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
        this.send(fullHttpResponse);
    }

    public void html(String html)
    {
        if (null == html) return;
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(getStatusCode()), Unpooled.wrappedBuffer(html.getBytes(CharsetUtil.UTF_8)), false);
        if (null == this.getContentType())
            this.setContentType(Constant.CONTENT_TYPE_HTML);
        this.send(fullHttpResponse);
    }


    /**
     * Send getHttpResponse body by ByteBuf.
     */
    public void body(ByteBuf byteBuf)
    {
        if (null == byteBuf) return;
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(getStatusCode()), byteBuf, false);
        this.send(fullHttpResponse);
    }


}